package com.sdu.cloudservice.service.impl;

import com.aliyuncs.ecs.model.v20140526.*;
import com.sdu.cloudservice.dao.ActualPriceDao;
import com.sdu.cloudservice.dao.InstanceInfoDao;
import com.sdu.cloudservice.dao.SpiderHistoryDao;
import com.sdu.cloudservice.entity.ActualPriceEntity;
import com.sdu.cloudservice.entity.InstanceInfoEntity;
import com.sdu.cloudservice.entity.SpiderHistoryEntity;
import com.sdu.cloudservice.service.GetPriceInfoService;
import com.sdu.cloudservice.service.SpiderStatus;
import com.sdu.cloudservice.util.AiliApiCallerUtil;
import com.sdu.cloudservice.util.DateUtil;
import com.sdu.cloudservice.util.EmailUtil;
import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
import org.apache.ibatis.session.SqlSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.text.ParseException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * Create by zekdot on 19-6-22.
 */
@Service
public class GetPriceInfoServiceImpl implements GetPriceInfoService {

    Logger logger = LoggerFactory.getLogger(GetPriceInfoServiceImpl.class);
    @Autowired
    private InstanceInfoDao instanceInfoDao;
    @Autowired
    private ActualPriceDao actualPriceDao;
    @Autowired
    private SqlSession sqlSession;
    @Autowired
    private SpiderHistoryDao spiderHistoryDao;
    /**
     * 获取地域列表
     * @return [{"localName":"华北 1","regionId":"cn-qingdao"}]
     */
    private JSONArray getRegionList(){
        AiliApiCallerUtil caller = new AiliApiCallerUtil();
        // 构造请求
        DescribeRegionsRequest describeRegionsRequest = new DescribeRegionsRequest();
        // 发送请求
        DescribeRegionsResponse describeRegionsResponse = caller.doAction(describeRegionsRequest);
        // 获取返回结果
        JSONObject jsonObject = JSONObject.fromObject(describeRegionsResponse);
        // 返回地区列表
        return jsonObject.getJSONArray("regions");
    }

    /**
     * 获取可用区列表
     * @param regionId 区域id
     * @return [{"localName":xxx,"zoneId":xxx,"availableInstanceTypes":[xxx]}]
     */
    private JSONArray getZoneList(String regionId){
        AiliApiCallerUtil caller = new AiliApiCallerUtil();
        // 构造请求
        DescribeZonesRequest request = new DescribeZonesRequest();
        // 设置请求的地域id
        request.setRegionId(regionId);
        // 对于查询是否可购买抢占式实例此项必填
        request.setSpotStrategy("SpotWithPriceLimit");
        // 后付费模式，抢占式实例必须是后付费模式
        request.setInstanceChargeType("PostPaid");
        // 发送请求
        DescribeZonesResponse response = caller.doAction(request);
        // 获取结果
        JSONObject jsonObject = JSONObject.fromObject(response);
        // 返回地域详情
        return jsonObject.getJSONArray("zones");
    }

    /**
     * 根据数据组装实例
     * @param id 节点id
     * @param value 节点值
     * @param level 节点等级
     * @param parentId 父节点id
     * @return
     */
    private InstanceInfoEntity getInstanceInfoFactory(String id,String value,int level,String parentId){
        InstanceInfoEntity instanceInfoEntity = new InstanceInfoEntity();
        instanceInfoEntity.setId(id);
        instanceInfoEntity.setValue(value);
        instanceInfoEntity.setLevel((byte) level);
        instanceInfoEntity.setParentId(parentId);
        return instanceInfoEntity;
    }
    @Override
    public void updateStructure() {
        List<InstanceInfoEntity> list = new ArrayList<>();

        // 获取地域列表
        JSONArray regionList = getRegionList();
        // 遍历地域列表
        for(int i=0 ;i<regionList.size() ;i++){
            // 获取当前地域
            JSONObject region = regionList.getJSONObject(i);
            // 存储 id 名称 层级为0
            list.add(getInstanceInfoFactory(region.getString("regionId"),region.getString("localName"),0,null));
            // System.out.println("存储"+region.getString("regionId")+" "+region.getString("localName")+" 0");
            // 获取可用区列表
            JSONArray zoneList = getZoneList(region.getString("regionId"));
            // 遍历可用区列表
            for(int j=0 ;j<zoneList.size(); j++){
                // 获取当前地域
                JSONObject zone = zoneList.getJSONObject(j);
                //System.out.println(zone);
                // 存储 id 名称 层级为1
                // System.out.println("存储"+zone.getString("zoneId")+" "+zone.getString("localName")+" 1 "+region.getString("regionId"));
                list.add(getInstanceInfoFactory(zone.getString("zoneId"),zone.getString("localName"),1,region.getString("regionId")));
                // 获取实例列表
                JSONArray instanceList = zone.getJSONArray("availableInstanceTypes");
                // 遍历实例列表
                for(int k=0 ;k<instanceList.size(); k++){
                    // 获取当前实例
                    String instance = instanceList.getString(k);
// 存储 id NULL 层级为2
                    // System.out.println("存储"+instance+" "+" null 2 "+zone.getString("zoneId"));
                    list.add(getInstanceInfoFactory(zone.getString("zoneId") + instance,instance,2,zone.getString("zoneId")));
                }
            }
        }
        // System.out.println("共爬取到"+list.size()+"条数据");
        // 新建一个mapper
        InstanceInfoDao mapper = sqlSession.getMapper(InstanceInfoDao.class);
        // 遍历所有的实例信息
        for (int i = 0; i < list.size(); i++) {
            // 取出当前实例
            InstanceInfoEntity instanceInfoEntity = list.get(i);
            // 存储实例
            mapper.insertInstance(instanceInfoEntity.getId(),instanceInfoEntity.getValue(),instanceInfoEntity.getLevel(),instanceInfoEntity.getParentId());
        }
    }

    /**
     * 查看两个价格条目是否价格相等
     * @param a1 价格条目1
     * @param a2 价格条目2
     * @return 是否相等
     */
    private boolean priceEqual(ActualPriceEntity a1,ActualPriceEntity a2){
        if(a1 == null || a2 == null)
            return false;
        if(Math.abs(a1.getOrigin() - a2.getOrigin()) <0.0001 && Math.abs(a1.getSpot() - a2.getSpot()) < 0.0001)
            return true;
        return false;
    }

    /**
     * 合并列表中的同类项
     * @param list 列表
     * @return 合并后的列表
     */
    private List<ActualPriceEntity> combineSamePrice(List<ActualPriceEntity> list){
        // 如果没有元素
        if(list.isEmpty()){
            // 直接返回
            return list;
        }
        // 从第一条遍历到倒数第二条
        for(int i=0 ;i<list.size()-1 ;i++){
            // 如果当前的价格和下一个时刻的价格一致
            if(priceEqual(list.get(i),list.get(i+1))){
                // 删除当前价格
                list.remove(i);
                i--;
            }
        }
        return list;
    }
    /**
     * 根据实例id和可用区id获取价格列表
     * @param zoneId 可用区id
     * @param instanceId 实例id
     * @param regionId 区域id
     * @param fromEntity 数据库中最后一条数据
     * @return 价格列表
     */
    private List<ActualPriceEntity> getPriceByInsAndZone(String regionId, String zoneId, String instanceId, ActualPriceEntity fromEntity,AiliApiCallerUtil caller ){
        // 返回列表
        List<ActualPriceEntity> resList = new ArrayList<>();

        // 准备结果列表
        List<DescribeSpotPriceHistoryResponse.SpotPriceType> result = new ArrayList<DescribeSpotPriceHistoryResponse.SpotPriceType>();
        // 设置偏移为0
        int offset = 0;
        while (true) {
            // 预备请求
            DescribeSpotPriceHistoryRequest request = new DescribeSpotPriceHistoryRequest();
            // 设置超时时间
            request.setConnectTimeout(7000);
            // 设置地域
            request.setRegionId(regionId);
            // 可用区必填
            request.setZoneId(zoneId);
            // 设置实例id
            request.setInstanceType(instanceId);
            // 设置网络类型，必填
            request.setNetworkType("vpc");
            // 是否 I/O 优化类型，DescribeZones 返回的 IoOptimized，选填
            request.setIoOptimized("optimized");
            // 价格开始时间，如果参考不为空
            if(fromEntity != null) {
                request.setStartTime(fromEntity.getTime());
            }else{
                // 否则，设置为30天前
                request.setStartTime(DateUtil.dateTime2AliStr(DateUtil.calcElapseDay(-30,new Date())));
            }
//            request.setEndTime("2019-06-20T01:45:08Z");//价格结束时间，选填
            // 设置当前偏移
            request.setOffset(offset);
            // 发送请求
            DescribeSpotPriceHistoryResponse response = caller.doAction(request);
            // 如果收到了应答
            if (response != null && response.getSpotPrices() != null) {
                // 添加应答信息
                result.addAll(response.getSpotPrices());
            }
            // 如果没有更多了
            if (response.getNextOffset() == null || response.getNextOffset() == 0) {
                break;
            } else {
                // 得到下一个偏移
                offset = response.getNextOffset();
            }
        }
        // 如果结果不为空
        if (!result.isEmpty()) {
            // 遍历结果列表
            for(int i=0; i<result.size() ;i++){
                // 获取当前处理的结果
                DescribeSpotPriceHistoryResponse.SpotPriceType spotPriceType = result.get(i);
                // 新建一个价格实体
                ActualPriceEntity actualPriceEntity = new ActualPriceEntity();
                // 设置可用区id
                actualPriceEntity.setZoneId(zoneId);
                // 设置实例id
                actualPriceEntity.setInstanceId(instanceId);
                try {
                    // 设置最近的时间点
                    actualPriceEntity.setTime(DateUtil.getNearestPoint(spotPriceType.getTimestamp()));
                } catch (ParseException e) {
                    e.printStackTrace();
                }
                // 设置原始价格
                actualPriceEntity.setOrigin(spotPriceType.getOriginPrice());
                // 设置竞价
                actualPriceEntity.setSpot(spotPriceType.getSpotPrice());
//                System.out.println("开始时间为"+fromEntity.getTime());
//                System.out.println("最后时间为"+result.get(result.size()-1).getTimestamp());
                // 如果最后的时间和开始的时间一致，即数据没有更新
                if(fromEntity !=null && result.get(result.size()-1).getTimestamp().equals(fromEntity.getTime())){
                    // 返回空
                    return resList;
                }
                // 如果是第一个并且开始时间和当前时间一致及价格一致
                if(fromEntity !=null && i==0 && spotPriceType.getTimestamp().equals(fromEntity.getTime()) && spotPriceType.getOriginPrice() - fromEntity.getOrigin() < 0.0001 && spotPriceType.getSpotPrice() - fromEntity.getSpot() < 0.0001){
                    // 跳过
                    continue;
                }
                // 检查和上一个是否重复，并且不是列表中最后一个，否则直接加入用于标示此次爬取结束
                if(i != result.size() -1 && resList.size() > 0){
                    // 获取最后一个
                    ActualPriceEntity lastEntity = resList.get(resList.size()-1);
                    // 如果可用区、实例名、时间都相同
                    if(actualPriceEntity.getZoneId().equals(lastEntity.getZoneId())&&actualPriceEntity.getInstanceId().equals(lastEntity.getInstanceId())&&actualPriceEntity.getTime().equals(lastEntity.getTime())){
                        // 跳过这个
                        continue;
                    }
                    //System.out.println(spotPriceType.getTimestamp() + "--->spotPrice:" + spotPriceType.getSpotPrice() + "---->originPrice:" + spotPriceType.getOriginPrice());
                    // 如果原价和竞价一样
                    if(Math.abs(actualPriceEntity.getOrigin() - lastEntity.getOrigin()) < 0.0001 && Math.abs(actualPriceEntity.getSpot() - lastEntity.getSpot()) < 0.0001 ){
                        // 跳过这个，便于压缩
                        continue;
                    }
                    // System.out.println("价格发生变化");

                }
                // 添加到列表中
                resList.add(actualPriceEntity);

            }
            //System.out.println(result.size());
        } else {

        }
        return resList;
    }
    @Override
    public void updatePrice() {
        // 设置调用工具
        AiliApiCallerUtil caller = new AiliApiCallerUtil();
        // 关闭演示状态
        SpiderStatus.isShow = false;
        // 设置状态为开始工作
        SpiderStatus.isWorking = true;

        // 设置当前时间为开始时间
        SpiderStatus.workingStartTime = DateUtil.dateTime2Str(new Date());

        StringBuilder emailContentBuilder = new StringBuilder();
        logger.info("爬虫于" + SpiderStatus.workingStartTime + "开始工作");

        emailContentBuilder.append("爬虫于" + SpiderStatus.workingStartTime + "开始工作\n");
        try {
            EmailUtil.send("zekdot@qq.com",emailContentBuilder.toString());
        } catch (Exception e) {
            e.printStackTrace();
        }
        List<InstanceInfoEntity> regions = instanceInfoDao.getAllRegion();

        // 申请新空间
        SpiderStatus.regionsSuccessNum = new int[regions.size()];
        SpiderStatus.regionName = new String[regions.size()];
        // 设置名称数组,初始化成功数组
        for(int i=0;i<regions.size();i++){
            SpiderStatus.regionName[i] = regions.get(i).getValue();
            SpiderStatus.regionsSuccessNum[i] = 0;
        }
        // 设置区域总进度
        SpiderStatus.curRegionProgress[1] = regions.size();
        // 从数据库遍历所有区域
        for(int i=0 ;i<regions.size() ;i++){
            // 结果列表
            List<ActualPriceEntity> resList = new ArrayList<>();
            // 得到当前处理的区域
            InstanceInfoEntity region = regions.get(i);
            logger.info("当前查询区域"+region.getValue());
            // System.out.println("当前查询区域"+region.getValue());
            // 设置当前处理区域名称
            SpiderStatus.curRegion = region.getValue();
            // 设置当前区域进度
            SpiderStatus.curRegionProgress[0] = i;
            // 根据区域id获取可用区列表
            List<InstanceInfoEntity> zones = instanceInfoDao.getZoneByRegionId(region.getId());
            // 设置可用区总进度
            SpiderStatus.curZoneProgress[1] = zones.size();
            // 遍历所有可用区
            for(int j=0 ;j<zones.size() ;j++){
                // 得到当前可用区
                InstanceInfoEntity zone = zones.get(j);
                logger.info("当前查询可用区"+zone.getValue());
                // System.out.println("当前查询可用区"+zone.getValue());
                // 设置当前可用区名称
                SpiderStatus.curZone = zone.getValue();
                // 设置当前可用区进度
                SpiderStatus.curZoneProgress[0] = j;
                // 根据区域id获取实例列表
                List<InstanceInfoEntity> instances = instanceInfoDao.getInstanceByZoneId(zone.getId());
                // 设置实例总进度
                SpiderStatus.curInstanceProgress[1] = instances.size();
                // 遍历所有实例
                for(int k=0 ;k<instances.size() ;k++){
                    InstanceInfoEntity instance = instances.get(k);
                    logger.info("    当前查询实例"+instance.getValue());
                    // System.out.println("    当前查询实例"+instance.getValue());
                    // 设置当前实例名称
                    SpiderStatus.curInstance = instance.getValue();
                    // 设置当前实例进度
                    SpiderStatus.curInstanceProgress[0] = k;
                    // 查询最后一条记录
                    ActualPriceEntity actualPriceEntity = actualPriceDao.getActualPriceByZoneIdAndInstanceId(zone.getId(),instance.getValue());
                    // System.out.println(actualPriceEntity.getTime());
                    //String fromTime = null;
                    // 如果没查到或者最后一条记录距今超过30天
                    try {
                        if(actualPriceEntity == null || DateUtil.getIntervalDays(new Date(),DateUtil.aliStr2Date(actualPriceEntity.getTime()))>30){
                            // 设置fromTime为30天前
                            // fromTime = DateUtil.dateTime2AliStr(DateUtil.calcElapseDay(-30,new Date()));
                            actualPriceEntity = null;
                        }else {
                            // 设置fromTime为最后一条的
                            // fromTime = actualPriceEntity.getTime();
                        }
                    } catch (ParseException e) {
                        e.printStackTrace();
                    }
                    // 查询价格实体并加入列表
                    List<ActualPriceEntity> curList = null;
                    try {
                        curList = getPriceByInsAndZone(region.getId(), zone.getId(), instance.getValue(), actualPriceEntity, caller);
                    }catch (Exception e){
                        e.printStackTrace();
                        logger.error(region.getValue() + " " + zone.getValue() + " " + instance.getValue() + "出现异常");
                        // System.out.println(region.getValue() + " " + zone.getValue() + " " + instance.getValue() + "出现异常");
                        // 设置为非工作状态
                        SpiderStatus.isWorking = false;
                        // 设置为演示状态
                        SpiderStatus.isShow = true;
                        try {
                            EmailUtil.send("zekdot@qq.com","获取发生致命性错误" + e.getMessage());
                        } catch (Exception e1) {
                            e1.printStackTrace();
                        }
                        return ;
                    }
                    if(curList == null){
                        continue;
                    }
                    // 如果之前已经有了项目，合并同类项
                    if(actualPriceEntity != null){
                        curList = combineSamePrice(curList);
                    }
                    logger.info("查询到了"+curList.size());
                    // System.out.println("查询到了"+curList.size());
                    //如果只抓到了一条并且和最后一条的价格相同,并且最后一条的分钟为0
                    if(actualPriceEntity != null && DateUtil.isZeroMinute(actualPriceEntity.getTime()) &&curList.size() == 1 && priceEqual(curList.get(0),actualPriceEntity)) {
                        // System.out.println("更新之前为"+actualPriceEntity.getZoneId()+" "+actualPriceEntity.getInstanceId()+" "+actualPriceEntity.getTime());
                        // 只需要更新日期，不需要添加到列表中
                        actualPriceDao.updateTimeByZoneIdAndInsId(curList.get(0).getTime(),actualPriceEntity.getZoneId(),actualPriceEntity.getInstanceId(),actualPriceEntity.getTime());
                        // System.out.println("更新为"+curList.get(0).getTime());
                    }else{
                        // 否则再添加到列表中
                        resList.addAll(curList);
                    }


                }
            }
            // 每次运行完一个区域更新一次数据库
            logger.info("查询到了" + resList.size());
            // System.out.println("查询到了"+resList.size());
            // 新建一个mapper
            ActualPriceDao mapper = sqlSession.getMapper(ActualPriceDao.class);
            // 遍历所有的实例信息
            for (int j = 0; j < resList.size(); j++) {
                // 取出当前实例
                ActualPriceEntity actualPriceEntity = resList.get(j);
                // 存储实例
                try {
                    mapper.insertIntoActualPrice(actualPriceEntity.getZoneId(), actualPriceEntity.getInstanceId(),
                            actualPriceEntity.getTime(), actualPriceEntity.getOrigin(), actualPriceEntity.getSpot()
                    );
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
            // 获取当前区域成功数字然后赋值
            SpiderStatus.regionsSuccessNum[i] = resList.size();
        }
        // 设置状态为结束工作
        SpiderStatus.isWorking = false;
        // 设置当前时间为结束时间
        SpiderStatus.workingEndTime = DateUtil.dateTime2Str(new Date());
        logger.info("爬虫于" + SpiderStatus.workingEndTime + "结束工作");
        emailContentBuilder.append("爬虫于" + SpiderStatus.workingEndTime + "结束工作\n");
        int sum = 0;
        for(int i=0;i<SpiderStatus.regionsSuccessNum.length;i++){
            sum += SpiderStatus.regionsSuccessNum[i];
        }
        emailContentBuilder.append("共爬取" + sum + "条数据");
        // 爬虫存储此次工作记录
        SpiderHistoryEntity spiderHistoryEntity = SpiderStatus.toHistoryEntity();
        spiderHistoryDao.insertHistory(spiderHistoryEntity);
        // 发送邮件
        try {
            EmailUtil.send("zekdot@qq.com",emailContentBuilder.toString());
        } catch (Exception e) {
            e.printStackTrace();
        }
        // 开启演示状态
        SpiderStatus.isShow = true;
    }

    public static void main(String args[]){
        GetPriceInfoServiceImpl getPriceInfoService = new GetPriceInfoServiceImpl();
        ActualPriceEntity actualPriceEntity1 = new ActualPriceEntity();
        actualPriceEntity1.setSpot(0.12);
        actualPriceEntity1.setOrigin(10.52);
        ActualPriceEntity actualPriceEntity2 = new ActualPriceEntity();
        actualPriceEntity2.setSpot(0.12);
        actualPriceEntity2.setOrigin(10.51);
        ActualPriceEntity actualPriceEntity3 = new ActualPriceEntity();
        actualPriceEntity3.setSpot(0.12);
        actualPriceEntity3.setOrigin(10.51);
        List<ActualPriceEntity> list = new ArrayList<>();
        list.add(actualPriceEntity1);
        list.add(actualPriceEntity2);
        list.add(actualPriceEntity3);
        System.out.println(getPriceInfoService.combineSamePrice(list).size());
        //System.out.println(getPriceInfoService.priceEqual(actualPriceEntity1,actualPriceEntity2));
    }
}
